%{
#include<iostream>
#include<stdio.h>
#include <string.h>
#include"head.h"
extern "C"{
void yyerror(const char *s);
extern int yylex(void);
}
extern char yytext[];
extern int column;
#define YYSTYPE node*
extern int yylex();
extern FILE *yyin;
void count();
%}
%token IDENTIFIER CONSTANT STRING_LITERAL 
%token TRUE FALSE
%token INCLUDE PRINT

%token OUTPUT_OP   // video v >> "a.mpg"
%token INPUT_OP    // video v << "a.mpg"

%left '~'     // 1~v~1
%left RESIZE_OP CUT_OP ZOOM_OP // =>, %>, *>
%left ITOV_OP // i ** time
%left '@'     // 1~v~1

%left '+' '-'
%left '*' '/' '%'
%left '<' '>' LE_OP GE_OP
%left EQ_OP NE_OP
%left OR
%left AND
%left NOT

%token BOOL INT FLOAT VOID IMAGE VIDEO PIXEL 

%token IF ELSE WHILE FOR RETURN TO ELIF 
%token DEF PARAM_OP //TODO:name changed. originally '<-' = INPUT_OP. now PARAM_OP

%start ss

%%
ss:program { DFS* dfs=new DFS();
             cout<<endl; 
             //dfs->print($1); 
             dfs->rebuild($1);}
   ;
program    
    : statement 
           { node *np=new node("program");
             np->node_list.push_back($1); $$=np; }            
    | statement program   //consider statement as a child_node of program and insert it in the front of program's node_list
           {  vector<node*>::iterator it=$2->node_list.begin(); 
              $2->node_list.insert(it,$1);
              $$=$2;}
    ;
statement     // regard all things in the right of rule as some kind of statement
    : variable_declaration {$$=$1;}
    | compound_statement {$$ = $1;}   
    | for_statement  {$$ = $1;}
    | function_call_statement  {$$ = $1;}
    | if_statement  {$$ = $1;}
    | return_statement  {$$ = $1;}
    | assignment_statement  {$$ = $1;}
    | function_definition  {$$ = $1;}
    | include_statement  //TODO: New Rule
    | print_statement  //TODO: New Rule
    | input_statement  //TODO: New Rule // v << "hi.avi"
    | output_statement // TODO: New Rule // v >> "hi.avi"
    ;

include_statement
    : INCLUDE STRING_LITERAL ';' //TODO: New Rule
    ; 

print_statement
    : PRINT constant_expression ';'//TODO: New Rule
    ; 

input_statement
    : left_value INPUT_OP STRING_LITERAL ';' //TODO: New Rule
    ;

output_statement
    : constant_expression OUTPUT_OP STRING_LITERAL ';'//TODO: New Rule
    ;

variable_declaration
    : type_name var_declarator_initializer_list ';' 
           {  node* ns= new node("variable_declaration"); 
              ns->node_list.push_back($1);
              ns->node_list.push_back($2);
              $$=ns;}
    ;

type_name  /* int(int, int)[](int, int) */
    : basic_type
           { node* np=new node("type_name");
             np->node_list.push_back($1);
             $$=np;}
    | type_name '[' ']'  /* int[] is an array of int's*/
           { $1->node_list.push_back($2);
             $1->node_list.push_back($3);
             $$=$1;}
    | type_name '[' constant_expression ']' /* int[5] is an int array of size 5*/
           { $1->node_list.push_back($2);
             $1->node_list.push_back($3);
             $1->node_list.push_back($4);
             $$=$1;}
    | type_name '(' ')'            /* int() is a function that returns an int */
           { $1->node_list.push_back($2);
             $1->node_list.push_back($3);
             $$=$1;}
    | type_name '(' parameter_type_declaration_list ')' /* int(int, int) */ //TODO: RULE CHANGED
           { $1->node_list.push_back($2);
             $1->node_list.push_back($3);
             $1->node_list.push_back($4);
             $$=$1;} 
    ;
basic_type
    : INT       {$$=$1;}
    | FLOAT     {$$=$1;}
    | BOOL      {$$=$1;}
    | IMAGE       {$$=$1;}
    | VIDEO     {$$=$1;}
    | PIXEL     {$$=$1;}
    | VOID      {$$=$1;}
    ;

constant_expression
    : constant_expression '+' constant_expression
            {node *nc=new node(" constant_expression");
             nc->node_list.push_back($1);
             nc->node_list.push_back($2);
             nc->node_list.push_back($3);
             $$=nc;} 
    | constant_expression '-' constant_expression
            {node *nc=new node(" constant_expression");
             nc->node_list.push_back($1);
             nc->node_list.push_back($2);
             nc->node_list.push_back($3);
             $$=nc;} 
    | constant_expression '*' constant_expression
            {node *nc=new node(" constant_expression");
             nc->node_list.push_back($1);
             nc->node_list.push_back($2);
             nc->node_list.push_back($3);
             $$=nc;} 
    | constant_expression '/' constant_expression
            {node *nc=new node(" constant_expression");
             nc->node_list.push_back($1);
             nc->node_list.push_back($2);
             nc->node_list.push_back($3);
             $$=nc;} 
    | constant_expression '%' constant_expression
            {node *nc=new node(" constant_expression");
             nc->node_list.push_back($1);
             nc->node_list.push_back($2);
             nc->node_list.push_back($3);
             $$=nc;}    
    | constant_expression '>' constant_expression
            {node *nc=new node(" constant_expression");
             nc->node_list.push_back($1);
             nc->node_list.push_back($2);
             nc->node_list.push_back($3);
             $$=nc;} 
    | constant_expression '<' constant_expression
            {node *nc=new node(" constant_expression");
             nc->node_list.push_back($1);
             nc->node_list.push_back($2);
             nc->node_list.push_back($3);
             $$=nc;} 
    | constant_expression LE_OP constant_expression
            {node *nc=new node(" constant_expression");
             nc->node_list.push_back($1);
             nc->node_list.push_back($2);
             nc->node_list.push_back($3);
             $$=nc;} 
    | constant_expression GE_OP constant_expression
            {node *nc=new node(" constant_expression");
             nc->node_list.push_back($1);
             nc->node_list.push_back($2);
             nc->node_list.push_back($3);
             $$=nc;} 
    | constant_expression EQ_OP constant_expression
            {node *nc=new node(" constant_expression");
             nc->node_list.push_back($1);
             nc->node_list.push_back($2);
             nc->node_list.push_back($3);
             $$=nc;} 
    | constant_expression NE_OP constant_expression
            {node *nc=new node(" constant_expression");
             nc->node_list.push_back($1);
             nc->node_list.push_back($2);
             nc->node_list.push_back($3);
             $$=nc;} 
    | constant_expression AND constant_expression
            {node *nc=new node(" constant_expression");
             nc->node_list.push_back($1);
             nc->node_list.push_back($2);
             nc->node_list.push_back($3);
             $$=nc;} 
    | constant_expression OR constant_expression
            {node *nc=new node(" constant_expression");
             nc->node_list.push_back($1);
             nc->node_list.push_back($2);
             nc->node_list.push_back($3);
             $$=nc;} 
    | constant_expression '~' constant_expression     //TODO: NEW RULE
    | constant_expression '@' constant_expression    //TODO: NEW RULE
    | constant_expression RESIZE_OP constant_expression    //TODO: NEW RULE
    | constant_expression CUT_OP constant_expression    //TODO: NEW RULE
    | constant_expression ZOOM_OP constant_expression    //TODO: NEW RULE
    | constant_expression ITOV_OP constant_expression    //TODO: NEW RULE
    | constant_expression '[' constant_expression ':' constant_expression ']'
                        //TODO: NEW RULE  */ //FIXME: RAISE 22 SHIFT/REDUCE CONFLICT
    | unary_expression {$$=$1;}
    ;



var_declarator_initializer_list
    : var_declarator_initializer
            {node * nv=new node("var_declarator_initializer_list"); 
             nv->node_list.push_back($1);
             $$=nv;}
    | var_declarator_initializer_list ',' var_declarator_initializer /* a, b=1, c */
            {$1->node_list.push_back($2);
             $1->node_list.push_back($3);
             $$=$1;}
    ;

var_declarator_initializer
   : IDENTIFIER    //TODO: Rule Changed. originally var_declarator, 
            {$$ = $1;}
    | IDENTIFIER '=' constant_expression
            {node * nv=new node("var_declarator_initializer"); 
             nv->node_list.push_back($1);
             nv->node_list.push_back($2);
             nv->node_list.push_back($3);
             $$=nv;}
    | IDENTIFIER INPUT_OP STRING_LITERAL  //TODO: New Rule: video v << "a.mpg"; 
    ;

/*
var_declarator   //TODO: Rule Deleted.
    : IDENTIFIER {$$=$1;} 
    | IDENTIFIER '(' parameter_value_list ')'
                {node *nv = new node("var_declarator");
                 nv->node_list.push_back($1);
                 nv->node_list.push_back($2);
                 nv->node_list.push_back($3);
                 nv->node_list.push_back($4);
                 $$ = nv;}
    ;
*/

unary_expression
    : unary_direct_expression {$$=$1;}
    | unary_operator unary_direct_expression /* do not allow multiple unary operators */
           {node *n=new node("unary_expression");
            n->node_list.push_back($1);
            n->node_list.push_back($2);
            $$=n;}
    ;
unary_operator
    : '-' {$$=$1;}
    | NOT {$$=$1;}
    ;

unary_direct_expression
    : primary_expression {node *un = new node("unary_direct_expression");un->node_list.push_back($1);$$ = un;}
    | unary_direct_expression '[' constant_expression ']'
          {$1->node_list.push_back($2);
           $1->node_list.push_back($3);
           $1->node_list.push_back($4);
           $$=$1;}
    | unary_direct_expression '.' IDENTIFIER
          {$1->node_list.push_back($2);
           $1->node_list.push_back($3);
           $$=$1;}
    ;

primary_expression
    : IDENTIFIER      {$$=$1;}
    | CONSTANT        {$$=$1;}
    | STRING_LITERAL  {$$=$1;}
    | TRUE            {$$=$1;}
    | FALSE           {$$=$1;}
    | function_call   {$$=$1;}
    | '(' constant_expression ')'
           {node *n=new node("primary_expression");
            n->node_list.push_back($1);
            n->node_list.push_back($2);
            n->node_list.push_back($3);
            $$=n;}
    | '{' constant_expression_list '}'
           {node *n=new node("primary_expression");
            n->node_list.push_back($1);
            n->node_list.push_back($2);
            n->node_list.push_back($3);
            $$=n;}
    ;

constant_expression_list
    : constant_expression
           {node *n=new node("constant_expression_list");
            n->node_list.push_back($1);
            $$=n;}
    | constant_expression_list ',' constant_expression
          {$1->node_list.push_back($2);
           $1->node_list.push_back($3);
           $$=$1;}
    ;

compound_statement
    : '{' '}'  {node *csn = new node("compound_statement"); csn->node_list.push_back($1);csn->node_list.push_back($2); $$ = csn;}
    | '{' program '}' {node *csn = new node("compound_statement"); csn->node_list.push_back($1);csn->node_list.push_back($2); csn->node_list.push_back($3);$$ = csn;}
    ;

/*
statement_list
    : statement {node *stln = new node("statement_list");stln->node_list.push_back($1);$$ = stln;}
    | statement_list statement {$1->node_list.push_back($2); $$ = $1;}
    ;
*/
function_call_statement
    : function_call ';'  {$$ = $1;}
    ;

return_statement
    : RETURN ';' {node* rsn=new node("return_statement"); rsn->node_list.push_back($1); $$ = rsn;}
    | RETURN constant_expression ';' {node* rsn=new node("return_statement"); rsn->node_list.push_back($1);rsn->node_list.push_back($2); $$ = rsn;}
    ;

assignment_statement
    : left_value '=' constant_expression ';' 
                 {node* asn=new node("assignment_statement"); 
                  asn->node_list.push_back($1);
                  asn->node_list.push_back($2);
                  asn->node_list.push_back($3); 
                  $$ = asn;}
    ;


if_statement /* 2 shift/reduce conflicts */
    : IF constant_expression ':' statement 
                 {node* ifn=new node("if_statement"); 
                  ifn->node_list.push_back($1);
                  ifn->node_list.push_back($2);
                  ifn->node_list.push_back($3);
                  ifn->node_list.push_back($4); 
                  $$ = ifn;}
    | IF constant_expression ':' statement ELSE ':' statement
                 {node* ifn=new node("if_statement"); 
                  ifn->node_list.push_back($1);
                  ifn->node_list.push_back($2);
                  ifn->node_list.push_back($3);
                  ifn->node_list.push_back($4);
                  ifn->node_list.push_back($5);
                  ifn->node_list.push_back($6); 
                  $$ = ifn;}
    | IF constant_expression ':' statement elif_list ELSE ':' statement
                   {node* ifn=new node("if_statement"); 
                  ifn->node_list.push_back($1);
                  ifn->node_list.push_back($2);
                  ifn->node_list.push_back($3);
                  ifn->node_list.push_back($4);
                  ifn->node_list.push_back($5);
                  ifn->node_list.push_back($6);
                  ifn->node_list.push_back($7);
                  ifn->node_list.push_back($8); 
                  $$ = ifn;}
    ;

elif_list
    : ELIF constant_expression ':' statement
           {node *enl = new node("elif_list");
            enl->node_list.push_back($1);
            enl->node_list.push_back($2);
            enl->node_list.push_back($3);
            enl->node_list.push_back($4);
            $$ = enl;}
    | elif_list ELIF constant_expression ':' statement
           {$1->node_list.push_back($2);
            $1->node_list.push_back($3);
            $1->node_list.push_back($4);
            $1->node_list.push_back($5);
            $$ = $1;}
    ; 

for_statement
    : FOR left_value '=' constant_expression TO constant_expression ':' statement 
            {node *fn = new node("for_statement");
             fn->node_list.push_back($1);
             fn->node_list.push_back($2);
             fn->node_list.push_back($3);
             fn->node_list.push_back($4);
             fn->node_list.push_back($5);
             fn->node_list.push_back($6);
             fn->node_list.push_back($7);
             fn->node_list.push_back($8);
             $$ = fn;}
    ;

left_value
    : unary_direct_expression
    ;
/* should be like this:
    : IDENTIFIER  //TODO: Rule Changed.
    | left_value '[' constant_expression ']' 
    ;
but will raise 1 reduce/reduce conflict;
*/


parameter_value_list
    : parameter_direct_value_list  ',' parameter_value_assign_list
            {node *pn = new node("parameter_value_list"); 
             pn->node_list.push_back($1);
             pn->node_list.push_back($2);
             pn->node_list.push_back($3);
             $$ = pn;}
    | parameter_direct_value_list {$$ = $1;}  
    | parameter_value_assign_list {$$ = $1;}
    ;

parameter_direct_value_list 
    : constant_expression {node * pnl = new node("parateter_direct_value_list");pnl->node_list.push_back($1);$$ = pnl;}
    | parameter_direct_value_list  ',' constant_expression
                 {$1->node_list.push_back($2);
                  $1->node_list.push_back($3);
                  $$ = $1;}
    ;

parameter_value_assign_list
    : parameter_assign_value {node *pal = new node("paramter_value_assign_list");pal->node_list.push_back($1);$$ = pal;}
    | parameter_value_assign_list ',' parameter_assign_value
              {$1->node_list.push_back($2);
               $1->node_list.push_back($3);
               $$ = $1;}
    ;

parameter_assign_value
    : IDENTIFIER '=' constant_expression
              {node *pan = new node("paramter_assign_value");pan->node_list.push_back($2);pan->node_list.push_back($3);$$ = pan;}
    ;

function_call
    : unary_direct_expression '(' ')'
            {node * fn = new node("function_call");fn->node_list.push_back($1);fn->node_list.push_back($2);fn->node_list.push_back($3);$$ = fn;}
    | unary_direct_expression '(' parameter_value_list ')'
            {node *fn = new node("function_call");fn->node_list.push_back($1);fn->node_list.push_back($2);fn->node_list.push_back($3);fn->node_list.push_back($4);$$ = fn;}
    ;


parameter_type_declaration_list   //TODO:NEW RULE
    : type_name
    | parameter_type_declaration_list ',' type_name
    ;


parameter_declaration_list 
    : parameter_declaration
            {node *pl = new node("parameter_declaration_list");pl->node_list.push_back($1);$$ = pl;}
    | parameter_declaration_list ',' parameter_declaration
            {$1->node_list.push_back($2);
             $1->node_list.push_back($3);
             $$ = $1;}
    ;

parameter_declaration
    : type_name {$$ = $1;}
    | type_name IDENTIFIER 
           {node *pdn = new node("parameter_declaration");
            pdn->node_list.push_back($1);
            pdn->node_list.push_back($2);
            $$ = pdn;}
    | type_name IDENTIFIER '=' constant_expression
           {node *pdn = new node("parameter_declaration");
            pdn->node_list.push_back($1);
            pdn->node_list.push_back($2);
            pdn->node_list.push_back($3);
            pdn->node_list.push_back($4);
            $$ = pdn;}
    ;

function_definition /* def foo : int[](int, int) <- (int z) */
    : DEF IDENTIFIER ':' type_name PARAM_OP '(' ')' statement
            {node *fdn = new node("function_definition");
            fdn->node_list.push_back($1);
            fdn->node_list.push_back($2);
            fdn->node_list.push_back($3);
            fdn->node_list.push_back($4);
            fdn->node_list.push_back($5);
            fdn->node_list.push_back($6);
            fdn->node_list.push_back($7);
            fdn->node_list.push_back($8);
            $$ = fdn;}
    | DEF IDENTIFIER ':' type_name PARAM_OP '(' parameter_declaration_list ')' statement
           {node *fdn = new node("function_definition");
            fdn->node_list.push_back($1);
            fdn->node_list.push_back($2);
            fdn->node_list.push_back($3);
            fdn->node_list.push_back($4);
            fdn->node_list.push_back($5);
            fdn->node_list.push_back($6);
            fdn->node_list.push_back($7);
            fdn->node_list.push_back($8);
            fdn->node_list.push_back($8);
            $$ = fdn;}
    ;

%%
void yyerror(const char *s){
        std::cerr<<s<<std::endl;
}
int main(int argc, char**argv){
    
    ++argv, --argc; 
	if ( argc > 0 ){
        yyin = fopen( argv[0], "r" );
    }
    else{
        yyin = stdin;
    }
        yyparse();

        return 0;
}
 
